统一初始化

cpp11以前:

int x;			//全局作用域时,默认初始化
int x = 7;		//值初始化
int a[] = {7, 8};	//聚合初始化
string s;		//默认构造函数初始化
vector<int> v(10);	//构造函数初始化

cpp11后:统一初始化方式,让内置类型和自定义类型可以使用一样的初始化方式

int a{5};
int a = {5};
int a[] {7, 8};
int a[] = {7, 8};
vector<int> v = {7, 8};
vector<int> v{7, 8};

int func(vector<int>);
int i = func({1,2,3});

struct X {
    vector<int> v;
    int a[];
    X() : v{1, 2}, a{3, 4} {}//成员初始化器
}

vector<int>* p = new vector<int>{1,2,3,4};//new表达式
X x {};//默认初始化

tempalte<typename T> int foo(T);
int z = foo{X{1}};//显示构造

//花括号初始化不允许窄化转换
double d = 7.2;
int x = d;	//OK,x = 7
int y{d};	//error

//自定义类型可以通过将标准库类型 initializer_list 用作`初始化器列表构造函数`的参数,实现统一初始化
tempalte <typename T>
class vector {
public:
    vector(initializer_list<T>);//初始化器列表构造函数
}
vector<int> v3 {1,2,3};

类型推导auto和decltype

使用推导类型可以缩减类型名称。

使用auto来避免类型名称的多余重复。

C++11的auto仅支持推导变量类型,和弱化的支持函数返回类型推导

auto X {
public:
    auto f() -> int;
}
void use(int x, char* p){
    auto x2 = x*2;
    auto ch = p[x];
    auto p2 = p+2;
}

auto推导不出引用类型,需要手动声明;或者使用decltype

template<typename T> void f(T& r){
    auto v = r;			//v 是 T
    decltype(r) r2 = r; //r2是 T&
    auto &r3 = r;		//r3是 T&
}

auto一般会忽略顶层const,保留底层const。

特殊:

  • 当原变量被取地址时,它的顶层const会变成底层const,从而得到保留
  • 当原变量被引用时,它的顶层const也会保留
const int ci = 32, &cr = ci;
auto b = ci;	// b is int
auto c = cr;	// c is int
auto d = &ci;	// d is const int *,这里是底层const

auto &q = ci;	// q is int& const
auto &w = 32;	//error:w is int&,32 need const int&
const auto &e = 32;	//e is const int&
const int ci = 32, &cr = ci;
auto b = ci;	// b is int
auto c = cr;	// c is int
auto d = &ci;	// d is const int *,这里是底层const

auto &q = ci;	// q is int& const
auto &w = 32;	//error:w is int&,32 need const int&
const auto &e = 32;	//e is const int&

顶层const:描述变量本身

底层const:描述变量指向内容

auto推导时要有一致性:

const int i = 32;
int j = 32;
auto a = i, &b = j;	//OK: a is int, b is int&, auto is int
auto &c = i, *p1 = &i;	//OK: c is int& const, p1 is const int*, auto is const int(or int const)
auto &d = j, *p2 = &i;	//error: d is int&, p2 is const int*, 
						//auto is different: int 、const int

decltype的结果类型与表达式形式密切相关:

  • 使用不加括号的变量,返回变量的类型
  • 使用加括号的变量,返回表达式的类型

变量是一种可以作为赋值语句左值的特俗表达式

int i = 0;
decltype(i) d;//d is int
decltype((i)) e = i;//e is int&

decltype((variable))(注意是双层括号)的结果永远是引用,而decltype(variable)结果只有当variable本身就是一个引用时才是引用

范围for

范围for可以避免一些错误

void use(vector<int>& v, list<string>& list) {
	for(int x : v) cout << x << "\n";
    int sum = 0;
    for(auto i : {1,2,3,4,5}) sum+=i;
    for(string& s: list) s+=".cpp";
}

void error_use(vector<int>& v, list<string>& list) {
    for(int i = 0; i < imax; ++i)
        for(int j = 0; i < imax; ++j){}	//错误的嵌套循环
    for(int i = 0; i <= imax; ++i){}	//多循环了一次的错误
}

移动语义

调用函数时,参数按值传递,会产生拷贝,尽管通过使用 const &可以巧妙避免传入参数的拷贝,但对返回值还是存在拷贝行为。

//连续加法的需求,让其返回值不能是指针
Matrix operator+(const Matrix&, const Matrix&);

为了避免返回值时进行大量的数据复制,需要确保在实现返回时,构造函数复制的只是(指向自由存储区上的数据的)句柄,而不是所有元素。

class Matrix {
    double* elements;	//指向所有元素的指针
public:
    Matrix(Matrix&& a)	//移动构造
    {
        elements = a.elements;	//复制句柄
        a.elements = nullptr;	//现在 a 的析构函数不用做任何事情了
    }
}

当用于初始化或赋值的源对象马上就会被销毁时,移动拷贝要更好。

&&表示构造函数是一个移动构造函数,Matrix&& 称作右值引用,当用于模板参数时,右值引用的符号&&被叫做转发引用

移动语义蕴含着性能上的重大好处:它消除了代价高昂的临时变量。

xvalue、lvalue、prvalue的区别:谈一谈 C++ 中的值的类型 - 知乎 (zhihu.com)

资源管理指针(智能指针)

C++11提供了智能指针:

  • shared_ptr——代表共享所有权
  • unique_ptr——代表独占所有权(取代C++98中的auto_ptr)

智能指针作用:减少资源泄露和悬空指针

shared_ptr是传统的计数指针:指向同一对象的所有指针共享一个计数器。往往需要wead_ptr的配合。

void ole_use(Args a){
    auto q = new Blob(a);
	//...
    if(foo) throw Bad();//会泄露
    if(bar) return;		//会泄露
    //...
    delete q;			//容易遗忘
}
void newer_sue(Args a){
    auto p = unique_ptr<Blob>(new Blob(a));
    //...
    if(foo) throw Bad();//不会泄露
    if(bar) return;		//不会泄露
    //...
}
// 除非真的需要指针,否则,简单地使用局部变量会更好
void simplest_use(Args a){
    Blob b(a);
    //...
    if(foo) throw Bad();//不会泄露
    if(bar) return;		//不会泄露
    //...
}

nullptr

int* p = 99-55-44;	//空指针
int* q = 2;			//错误:2是int,不是int*

C++继承了C语言的NULL,但NULL在C++中定义为0。

int p0 = nullptr;
int* p1 = 99-55-44;	//可以,为了兼容性
int* p2 = NULL;		//可以,为了兼容性

int f(char*);
int f(int);

int x1 = f(nullptr);	//f(char*)
int x2 = f(0);			//f(int)

constexpr函数

C++11之前进行常量表达式求值还是用的(无类型的)宏,与C一样。

constexpr的目的:

  • 让编译期计算达到类型安全
  • 在编译期计算,提高效率
  • 支持嵌入式系统编程(尤其是ROM)
  • 支持元编程(非模板元编程)
  • 让编译期编程与“普通编程”非常相似

允许常量表达式中使用constexpr函数;允许在常量表达式中使用简单用户定义类型,叫字面量类型。

constexpr函数可以在编译期进行求值,无法访问非本地对象。

struct LengthInKM {
    constexpr explicit LengthInKM(double d) : val(d) { }
    constexpr double getValue() { return val; }
private:
    double val;
};

LengthInKM marks[] = { LengthInKM(2.3), LengthInKM(0.76) };

void f(int x) {
    int y1 = x;
    constexpr int y2 = x;	//错误,x不是常量,编译期x还不存在
    constexpr int y3 = 77;	//正确
}

用户定义字面量

内建类型有字面量,用户定义类型可通过重载字面量运算符operator"")实现自己的字面量,其本质是显示地使用构造函数。

constexpr Imaginary operator""i(long double x) {
    return Imaginary(x);
}

Imaginary a = 3.14i; //从而complex<double>(1.2,3.4) == 1.2+3.4i;

原始字符串字面量

和C一样,C++使用反斜杠作为转义字符,如果在字符串字面量中使用反斜杠,需要使用双反斜杠。

通常的正则表达式模式广泛使用反斜杠和双引号,所以模式很快变得混乱和容易出错。

//美国邮政编码
regex pattern1 {"\\w{2}\\s*\\d{5}(-\\d{4})?"};	//普通字符串字面量
regex pattern2 {R"(\w{2}\s*\d{5}(-\d{4})?)"};	//原始字符串字面量

lambda表达式

C++11的lambda只支持捕获值或引用,C++14添加了移动捕获和泛型lambda

lambda表达式的中括号表示从周围环境捕获变量,小括号表示参数,返回值可以从返回语句推导出来,如果没有返回语句则不会返回任何东西。

声明捕获有三种方式:

  • 隐式捕获:&捕获引用,=捕获值
  • 显示捕获列表
  • 声明隐式捕获,并声明显示捕获列表:显示捕获列表的捕获方式同隐式捕获相反

有特别的捕获方式:

  • [this]:通过引用捕获*this(它是对象的左值),而不是通过值捕获指针
  • [*this]:捕获本地实体的值
#include <iostream>
#include <algorithm>   // sort函数模板、for_each函数模板
#include <vector>
 
using namespace std;
 

//[capture list](parameter list)-> return type {function body}
int main ( )
{
    auto f1 = []() { cout << "lambda test" << endl; };
    f1();

    //use paramter list
    auto f2 = [](int a, int b) { cout << a << ' ' << b << endl; };
    f2(3, 4);

    //use capture list
    int a = 1;
    auto f3 = [a]() { cout << a << endl; }; //值捕获
    f3();
    auto f4 = [&a]() { a++; cout << a << endl; };//引用捕获
    f4();
    auto f5 = [&]() { a++; cout << a << endl; };//隐式捕获引用
    f5();
    auto f6 = [=]() { cout << a << endl; };//隐式捕获值
    f6();
    auto f7 = [=]() mutable { a++; cout << a << endl; }; //隐式捕获值,可变lambda,捕获的值在函数体改变不影响外面变量
    f7();
    cout << "after mutable" << a << endl;
    auto f8 = [&,a]() { cout << a << endl; };
    f8();
    auto f9 = [=, &a]() { a++; cout << a << endl; };
    f9();
}

元组tuple

元组是大小固定而成员类型可以不同的容器。可用于:

  • 作为返回类型,用于需要超过一个返回类型的函数
  • 同时赋值多个
void use() {
    string a, aa;
    vector b, bb;
    double c, cc;
    /* ... */
    auto r = make_tuple(a, b, c);	// 创建元组
    tie(aa, bb, cc) = r;			// 解包元组
    
    // C++14后,可以逐个获取
    aa = get<0>(r);
    bb = get<1>(r);
    cc = get<2>(r);
    
    // C++17后,添加了结构化绑定,真正意义上的解包
    auto [aaa, bbb, ccc] = r;
    
}